#ifndef SSY_MATRIX_COMMON_H
#define SSY_MATRIX_COMMON_H


#include "MutrixBase.h"

template<typename ElementType>
class MatrixCommon : public MatrixBase<ElementType> {
public:
	MatrixCommon(size_t row, size_t col) : MatrixBase(row, col) {
		SetType(MATRIX_COMMON);
		_mut.resize(GetRow());
		for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
			_mut[rIdx].resize(GetCol());
		}
	}
	MatrixCommon(const MatrixBase& matbase);
	~MatrixCommon(void) {
		for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
			vector<ElementType>().swap(_mut[rIdx]);
		}
		vector<vector<ElementType>>().swap(_mut);
	}

	const ElementType& GetValue(size_t rowidx, size_t colidx) const;
	bool SetValue(size_t rowidx, size_t colidx, const ElementType& element);
	void Print(void) const;

	MatrixCommon<ElementType> Transpose(void) const;
	MatrixCommon<ElementType>& operator=(const MatrixBase<ElementType>& matbase);
	const vector<ElementType>& operator[](size_t row) const { return _mut[row]; } 
	vector<ElementType>& operator[](size_t row) { 
		return const_cast<vector<ElementType>&>
			(static_cast<const MatrixCommon<ElementType>&>(*this)[row]); 
	}

	friend const MatrixCommon operator+(const MatrixBase& matbase1, const MatrixBase& matbase2) {
		return doAdd(matbase1, matbase2);
	}
	friend const MatrixCommon operator-(const MatrixBase& matbase1, const MatrixBase& matbase2) {
		return doSubtract(matbase1, matbase2);
	}
	friend const MatrixCommon operator*(const MatrixBase& matbase1, const MatrixBase& matbase2) {
		return doMultiply(matbase1, matbase2);
	}
	friend const MatrixCommon operator*(const MatrixBase& matbase, const ElementType& element) {
		return doElementMultiply(matbase, element);
	}
	friend const MatrixCommon operator*(const ElementType& element, const MatrixBase& matbase) {
		return doElementMultiply(matbase, element);
	}

private:
	bool PushValue(const ElementType& element);

	vector<vector<ElementType>> _mut; 
};

template<typename ElementType>
MatrixCommon<ElementType>::MatrixCommon(const MatrixBase& matbase) 
	: MatrixBase(matbase) {
	_mut.resize(GetRow());
	for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
		_mut[rIdx].resize(GetCol());
		for (size_t cIdx = 0; cIdx < GetCol(); cIdx++)
			assert(SetValue(rIdx, cIdx, matbase.GetValue(rIdx, cIdx)));
	}
}

template<typename ElementType>
bool MatrixCommon<ElementType>::PushValue(const ElementType& element) {
	if (_rowidx == GetRow()-1 && _colidx == GetCol()) {
		SetErrMsg("This mutrix is full, can't push");
		return false;
	}
	else {
		_mut[_rowidx][_colidx] = element;
		_colidx++;
		if (_rowidx < GetRow()-1 && _colidx == GetCol()) {
			_colidx = 0;
			_rowidx++;
		}
		return true;
	}
}

template<typename ElementType>
const ElementType& MatrixCommon<ElementType>::GetValue(size_t rowidx, size_t colidx) const {
	assert(rowidx < GetRow());
	assert(colidx < GetCol());
	return _mut[rowidx][colidx];
}

template<typename ElementType>
bool MatrixCommon<ElementType>::SetValue(size_t rowidx, size_t colidx, const ElementType& element) {
	if (rowidx < GetRow() && colidx < GetCol()) {
		_mut[rowidx][colidx] = element;
		return true;
	}
	SetErrMsg("uncorrect row or col input");
	return false;
}

template<typename ElementType>
void MatrixCommon<ElementType>::Print(void) const {
	cout << "[" << GetRow() << "," << GetCol() << "] "; 
	for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
		for (size_t cIdx = 0; cIdx < GetCol(); cIdx++)
			cout << GetValue(rIdx, cIdx) << " ";
	}
	cout << endl;
}

template<typename ElementType>
MatrixCommon<ElementType> MatrixCommon<ElementType>::Transpose(void) const {
	MatrixCommon<ElementType> matcomm(GetCol(), GetRow());
	for (size_t cIdx = 0; cIdx < GetCol(); cIdx++) {
		for (size_t rIdx = 0; rIdx < GetRow(); rIdx++) {
			matcomm.SetValue(cIdx, rIdx, GetValue(rIdx, cIdx));
		}
	}
	return matcomm;
}

template<typename ElementType>
MatrixCommon<ElementType>& MatrixCommon<ElementType>::operator=(const MatrixBase<ElementType>& matbase) {
	if (this != &matbase)
		MatrixBase::operator=(matbase);
	return (*this);
}

template<typename ElementType>
const MatrixCommon<ElementType> doAdd(const MatrixBase<ElementType>& matbase1, 
									const MatrixBase<ElementType>& matbase2) {
	assert(matbase1.GetRow() == matbase2.GetRow());
	assert(matbase1.GetCol() == matbase2.GetCol());
	MatrixCommon<ElementType> matsum(matbase1.GetRow(), matbase1.GetCol());
	matsum = matbase1;
	matsum += matbase2;
	return matsum;
}

template<typename ElementType>
const MatrixCommon<ElementType> doSubtract(const MatrixBase<ElementType>& matbase1, 
									const MatrixBase<ElementType>& matbase2) {
	assert(matbase1.GetRow() == matbase2.GetRow());
	assert(matbase1.GetCol() == matbase2.GetCol());
	MatrixCommon<ElementType> matdiff(matbase1.GetRow(), matbase1.GetCol());
	matdiff = matbase1;
	matdiff -= matbase2;
	return matdiff;
}

template<typename ElementType>
const MatrixCommon<ElementType> doMultiply(const MatrixBase<ElementType>& matbase1, 
									const MatrixBase<ElementType>& matbase2) {
	assert(matbase1.GetRow() == matbase2.GetCol());
	assert(matbase1.GetCol() == matbase2.GetRow());
	MatrixCommon<ElementType> matproduct(matbase1.GetRow(), matbase2.GetCol());
	ElementType element;
	for (size_t rIdx = 0; rIdx < matbase1.GetRow(); rIdx++) {
		for (size_t cIdx = 0; cIdx < matbase2.GetCol(); cIdx++) {
			element = 0;
			for (size_t tIdx = 0; tIdx < matbase1.GetCol(); tIdx++) 
				element += matbase1.GetValue(rIdx, tIdx) * matbase2.GetValue(tIdx, cIdx);
			assert(matproduct.SetValue(rIdx, cIdx, element));
		}
	}
	return matproduct;
}

template<typename ElementType>
const MatrixCommon<ElementType> doElementMultiply(const MatrixBase<ElementType>& matbase, 
												  const ElementType& element) {
	MatrixCommon<ElementType> matproduct(matbase);
	for (size_t rIdx = 0; rIdx < matproduct.GetRow(); rIdx++) {
		for (size_t cIdx = 0; cIdx < matproduct.GetCol(); cIdx++) {
			assert(matproduct.SetValue(rIdx, cIdx, matproduct.GetValue(rIdx, cIdx) * element));
		}
	}
	return matproduct;
}


#endif // SSY_MATRIX_COMMON_H

